1   /*
2    * Copyright (C) 2011 The Guava Authors
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package com.google.common.collect;
18  
19  import com.google.common.annotations.GwtCompatible;
20  import com.google.common.base.Function;
21  import com.google.common.base.Functions;
22  import com.google.common.collect.testing.MapInterfaceTest;
23  
24  import java.util.Collection;
25  import java.util.Iterator;
26  import java.util.Map;
27  import java.util.Set;
28  
29  import javax.annotation.Nullable;
30  
31  /**
32   * Tests for {@link Maps#transformValues} when the backing map's views
33   * have iterators that don't support {@code remove()}.
34   *
35   * @author Jared Levy
36   */
37  @GwtCompatible
38  public class MapsTransformValuesUnmodifiableIteratorTest extends MapInterfaceTest<String, String> {
39    // TODO(jlevy): Move shared code of this class and MapsTransformValuesTest
40    // to a superclass.
41  
42    public MapsTransformValuesUnmodifiableIteratorTest() {
43      super(true, true, false /*supportsPut*/, true, true, false);
44    }
45  
46    private static class UnmodifiableIteratorMap<K, V> extends ForwardingMap<K, V> {
47      final Map<K, V> delegate;
48  
49      UnmodifiableIteratorMap(Map<K, V> delegate) {
50        this.delegate = delegate;
51      }
52  
53      @Override protected Map<K, V> delegate() {
54        return delegate;
55      }
56  
57      @Override public Set<K> keySet() {
58        return new ForwardingSet<K>() {
59          @Override protected Set<K> delegate() {
60            return delegate.keySet();
61          }
62          @Override public Iterator<K> iterator() {
63            return Iterators.unmodifiableIterator(delegate.keySet().iterator());
64          }
65          @Override public boolean removeAll(Collection<?> c) {
66            return delegate.keySet().removeAll(c);
67          }
68          @Override public boolean retainAll(Collection<?> c) {
69            return delegate.keySet().retainAll(c);
70          }
71        };
72      }
73  
74      @Override public Collection<V> values() {
75        return new ForwardingCollection<V>() {
76          @Override protected Collection<V> delegate() {
77            return delegate.values();
78          }
79          @Override public Iterator<V> iterator() {
80            return Iterators.unmodifiableIterator(delegate.values().iterator());
81          }
82          @Override public boolean removeAll(Collection<?> c) {
83            return delegate.values().removeAll(c);
84          }
85          @Override public boolean retainAll(Collection<?> c) {
86            return delegate.values().retainAll(c);
87          }
88        };
89      }
90  
91      @Override public Set<Entry<K, V>> entrySet() {
92        return new ForwardingSet<Entry<K, V>>() {
93          @Override protected Set<Entry<K, V>> delegate() {
94            return delegate.entrySet();
95          }
96          @Override public Iterator<Entry<K, V>> iterator() {
97            return Iterators.unmodifiableIterator(delegate.entrySet().iterator());
98          }
99          @Override public boolean removeAll(Collection<?> c) {
100           return delegate.entrySet().removeAll(c);
101         }
102         @Override public boolean retainAll(Collection<?> c) {
103           return delegate.entrySet().retainAll(c);
104         }
105       };
106     }
107   }
108 
109   @Override protected Map<String, String> makeEmptyMap() {
110     Map<String, Integer> underlying = Maps.newHashMap();
111     return Maps.transformValues(
112         new UnmodifiableIteratorMap<String, Integer>(underlying), Functions.toStringFunction());
113   }
114 
115   @Override protected Map<String, String> makePopulatedMap() {
116     Map<String, Integer> underlying = Maps.newHashMap();
117     underlying.put("a", 1);
118     underlying.put("b", 2);
119     underlying.put("c", 3);
120     return Maps.transformValues(
121         new UnmodifiableIteratorMap<String, Integer>(underlying), Functions.toStringFunction());
122   }
123 
124   @Override protected String getKeyNotInPopulatedMap()
125       throws UnsupportedOperationException {
126     return "z";
127   }
128 
129   @Override protected String getValueNotInPopulatedMap()
130       throws UnsupportedOperationException {
131     return "26";
132   }
133 
134   /** Helper assertion comparing two maps */
135   private void assertMapsEqual(Map<?, ?> expected, Map<?, ?> map) {
136     assertEquals(expected, map);
137     assertEquals(expected.hashCode(), map.hashCode());
138     assertEquals(expected.entrySet(), map.entrySet());
139 
140     // Assert that expectedValues > mapValues and that
141     // mapValues > expectedValues; i.e. that expectedValues == mapValues.
142     Collection<?> expectedValues = expected.values();
143     Collection<?> mapValues = map.values();
144     assertEquals(expectedValues.size(), mapValues.size());
145     assertTrue(expectedValues.containsAll(mapValues));
146     assertTrue(mapValues.containsAll(expectedValues));
147   }
148 
149   public void testTransformEmptyMapEquality() {
150     Map<String, String> map = Maps.transformValues(
151         ImmutableMap.<String, Integer>of(), Functions.toStringFunction());
152     assertMapsEqual(Maps.newHashMap(), map);
153   }
154 
155   public void testTransformSingletonMapEquality() {
156     Map<String, String> map = Maps.transformValues(
157         ImmutableMap.of("a", 1), Functions.toStringFunction());
158     Map<String, String> expected = ImmutableMap.of("a", "1");
159     assertMapsEqual(expected, map);
160     assertEquals(expected.get("a"), map.get("a"));
161   }
162 
163   public void testTransformIdentityFunctionEquality() {
164     Map<String, Integer> underlying = ImmutableMap.of("a", 1);
165     Map<String, Integer> map = Maps.transformValues(
166         underlying, Functions.<Integer>identity());
167     assertMapsEqual(underlying, map);
168   }
169 
170   public void testTransformPutEntryIsUnsupported() {
171     Map<String, String> map = Maps.transformValues(
172         ImmutableMap.of("a", 1), Functions.toStringFunction());
173     try {
174       map.put("b", "2");
175       fail();
176     } catch (UnsupportedOperationException expected) {
177     }
178 
179     try {
180       map.putAll(ImmutableMap.of("b", "2"));
181       fail();
182     } catch (UnsupportedOperationException expected) {
183     }
184 
185     try {
186       map.entrySet().iterator().next().setValue("one");
187       fail();
188     } catch (UnsupportedOperationException expected) {
189     }
190   }
191 
192   public void testTransformRemoveEntry() {
193     Map<String, Integer> underlying = Maps.newHashMap();
194     underlying.put("a", 1);
195     Map<String, String> map
196         = Maps.transformValues(underlying, Functions.toStringFunction());
197     assertEquals("1", map.remove("a"));
198     assertNull(map.remove("b"));
199   }
200 
201   public void testTransformEqualityOfMapsWithNullValues() {
202     Map<String, String> underlying = Maps.newHashMap();
203     underlying.put("a", null);
204     underlying.put("b", "");
205 
206     Map<String, Boolean> map = Maps.transformValues(underlying,
207         new Function<String, Boolean>() {
208           @Override
209           public Boolean apply(@Nullable String from) {
210             return from == null;
211           }
212         }
213     );
214     Map<String, Boolean> expected = ImmutableMap.of("a", true, "b", false);
215     assertMapsEqual(expected, map);
216     assertEquals(expected.get("a"), map.get("a"));
217     assertEquals(expected.containsKey("a"), map.containsKey("a"));
218     assertEquals(expected.get("b"), map.get("b"));
219     assertEquals(expected.containsKey("b"), map.containsKey("b"));
220     assertEquals(expected.get("c"), map.get("c"));
221     assertEquals(expected.containsKey("c"), map.containsKey("c"));
222   }
223 
224   public void testTransformReflectsUnderlyingMap() {
225     Map<String, Integer> underlying = Maps.newHashMap();
226     underlying.put("a", 1);
227     underlying.put("b", 2);
228     underlying.put("c", 3);
229     Map<String, String> map
230         = Maps.transformValues(underlying, Functions.toStringFunction());
231     assertEquals(underlying.size(), map.size());
232 
233     underlying.put("d", 4);
234     assertEquals(underlying.size(), map.size());
235     assertEquals("4", map.get("d"));
236 
237     underlying.remove("c");
238     assertEquals(underlying.size(), map.size());
239     assertFalse(map.containsKey("c"));
240 
241     underlying.clear();
242     assertEquals(underlying.size(), map.size());
243   }
244 
245   public void testTransformChangesAreReflectedInUnderlyingMap() {
246     Map<String, Integer> underlying = Maps.newLinkedHashMap();
247     underlying.put("a", 1);
248     underlying.put("b", 2);
249     underlying.put("c", 3);
250     underlying.put("d", 4);
251     underlying.put("e", 5);
252     underlying.put("f", 6);
253     underlying.put("g", 7);
254     Map<String, String> map
255         = Maps.transformValues(underlying, Functions.toStringFunction());
256 
257     map.remove("a");
258     assertFalse(underlying.containsKey("a"));
259 
260     Set<String> keys = map.keySet();
261     keys.remove("b");
262     assertFalse(underlying.containsKey("b"));
263 
264     Iterator<String> keyIterator = keys.iterator();
265     keyIterator.next();
266     keyIterator.remove();
267     assertFalse(underlying.containsKey("c"));
268 
269     Collection<String> values = map.values();
270     values.remove("4");
271     assertFalse(underlying.containsKey("d"));
272 
273     Iterator<String> valueIterator = values.iterator();
274     valueIterator.next();
275     valueIterator.remove();
276     assertFalse(underlying.containsKey("e"));
277 
278     Set<Map.Entry<String, String>> entries = map.entrySet();
279     Map.Entry<String, String> firstEntry = entries.iterator().next();
280     entries.remove(firstEntry);
281     assertFalse(underlying.containsKey("f"));
282 
283     Iterator<Map.Entry<String, String>> entryIterator = entries.iterator();
284     entryIterator.next();
285     entryIterator.remove();
286     assertFalse(underlying.containsKey("g"));
287 
288     assertTrue(underlying.isEmpty());
289     assertTrue(map.isEmpty());
290     assertTrue(keys.isEmpty());
291     assertTrue(values.isEmpty());
292     assertTrue(entries.isEmpty());
293   }
294 
295   public void testTransformEquals() {
296     Map<String, Integer> underlying = ImmutableMap.of("a", 0, "b", 1, "c", 2);
297     Map<String, Integer> expected
298         = Maps.transformValues(underlying, Functions.<Integer>identity());
299 
300     assertMapsEqual(expected, expected);
301 
302     Map<String, Integer> equalToUnderlying = Maps.newTreeMap();
303     equalToUnderlying.putAll(underlying);
304     Map<String, Integer> map = Maps.transformValues(
305         equalToUnderlying, Functions.<Integer>identity());
306     assertMapsEqual(expected, map);
307 
308     map = Maps.transformValues(ImmutableMap.of("a", 1, "b", 2, "c", 3),
309         new Function<Integer, Integer>() {
310           @Override
311           public Integer apply(Integer from) {
312             return from - 1;
313           }
314         }
315     );
316     assertMapsEqual(expected, map);
317   }
318 
319   public void testTransformEntrySetContains() {
320     Map<String, Boolean> underlying = Maps.newHashMap();
321     underlying.put("a", null);
322     underlying.put("b", true);
323     underlying.put(null, true);
324 
325     Map<String, Boolean> map = Maps.transformValues(
326         underlying, new Function<Boolean, Boolean>() {
327           @Override
328           public Boolean apply(@Nullable Boolean from) {
329             return (from == null) ? true : null;
330           }
331         }
332     );
333 
334     Set<Map.Entry<String, Boolean>> entries = map.entrySet();
335     assertTrue(entries.contains(Maps.immutableEntry("a", true)));
336     assertTrue(entries.contains(Maps.immutableEntry("b", (Boolean) null)));
337     assertTrue(entries.contains(
338         Maps.immutableEntry((String) null, (Boolean) null)));
339 
340     assertFalse(entries.contains(Maps.immutableEntry("c", (Boolean) null)));
341     assertFalse(entries.contains(Maps.immutableEntry((String) null, true)));
342   }
343 
344   @Override public void testKeySetRemoveAllNullFromEmpty() {
345     try {
346       super.testKeySetRemoveAllNullFromEmpty();
347     } catch (RuntimeException tolerated) {
348       // GWT's HashMap.keySet().removeAll(null) doesn't throws NPE.
349     }
350   }
351 
352   @Override public void testEntrySetRemoveAllNullFromEmpty() {
353     try {
354       super.testEntrySetRemoveAllNullFromEmpty();
355     } catch (RuntimeException tolerated) {
356       // GWT's HashMap.entrySet().removeAll(null) doesn't throws NPE.
357     }
358   }
359 }